package cn.liziguo.utils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author Liziguo
 * @date 2021/12/19 23:32
 */
public final class CaptchaUtils {

    /**
     * 所有字母和数字
     */
    private static final String ALL = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";

    /**
     * 去除容易混淆的字母
     */
    private static final String UNAMBIGUOUS = "234678wertyuipadfghjkzxcvbnmQWERTYUIPADFGHJKLZXCVBNM";

    /**
     * 字体颜色和背景颜色的最小色差
     */
    private static final int MIN_TOLERANCE = 100;

    /**
     * 噪声率
     */
    private static final float RATIO = 0.05f;

    /**
     * 本地字体库
     */
    private static final Font[] FONTS = initFonts();

    /**
     * 加载fonts文件夹下的所有字体
     *
     * @return 字体数组
     */
    private static Font[] initFonts() {
        final File file = new File("fonts");
        final File[] files = file.listFiles();
        if (files == null) {
            throw new RuntimeException("未找到字体目录");
        }
        if (files.length == 0) {
            throw new RuntimeException("fonts目录下至少放置一种字体");
        }
        final Font[] fonts = new Font[files.length];
        for (int i = 0, len = files.length; i < len; i++) {
            try {
                fonts[i] = Font.createFont(Font.TRUETYPE_FONT, files[i]);
            } catch (FontFormatException | IOException e) {
                e.printStackTrace();
                throw new RuntimeException("字体[" + files[i].getName() + "]初始化异常");
            }
        }
        return fonts;
    }

    /**
     * 获取随机颜色
     *
     * @param random random
     * @return 随机颜色
     */
    public static Color randomColor(final Random random) {
        return new Color(random.nextInt(0x1000000));
    }

    /**
     * 计算c1 c2的色差
     *
     * @param c1 颜色1
     * @param c2 颜色2
     * @return 色差
     */
    public static int tolerance(final Color c1, final Color c2) {
        final int r = c1.getRed() - c2.getRed();
        final int g = c1.getGreen() - c2.getGreen();
        final int b = c1.getBlue() - c2.getBlue();
        int max = 0;
        if (r > max) max = r;
        if (g > max) max = g;
        if (b > max) max = b;
        int min = 0;
        if (r < min) min = r;
        if (g < min) min = g;
        if (b < min) min = b;
        return max - min;
    }

    /**
     * 生成长度为len的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomStr(final int len) {
        return randomStr(ThreadLocalRandom.current(), len);
    }

    /**
     * 生成长度为len的随机字符串
     *
     * @param random random
     * @param len    长度
     * @return 随机字符串
     */
    public static String randomStr(final Random random, final int len) {
        final String c = ALL;
        final StringBuilder b = new StringBuilder();
        for (int i = 0; i < len; i++) {
            b.append(c.charAt(random.nextInt(c.length())));
        }
        return b.toString();
    }

    /**
     * 生成长度为len不易混淆的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomCode(final int len) {
        return randomCode(ThreadLocalRandom.current(), len);
    }

    /**
     * 生成长度为len不易混淆的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomCode(final Random random, final int len) {
        final String c = UNAMBIGUOUS;
        final StringBuilder b = new StringBuilder();
        for (int i = 0; i < len; i++) {
            b.append(c.charAt(random.nextInt(c.length())));
        }
        return b.toString();
    }

    /**
     * 生成180 * 60 字体大小为60 长度为4 的随机验证码
     * 最佳宽高计算公式 height = fontSize  width = height * len * 0.75
     *
     * @return 验证码
     */
    public static Captcha create() {
        return create(randomCode(4), 180, 60, 60);
    }

    /**
     * 生成指定字符串验证码 最佳宽高计算公式 height = fontSize  width = height * len * 0.75
     *
     * @param code     验证码
     * @param width    图片宽度
     * @param height   图片高度
     * @param fontSize 字体大小
     * @return 验证码
     */
    public static Captcha create(final String code, final int width, final int height, final int fontSize) {
        final ThreadLocalRandom random = ThreadLocalRandom.current();
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        final Graphics2D g = image.createGraphics();
        //抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        //干扰线 噪点粗细
        final float stroke = fontSize / 40f;

        //背景颜色
        final Color backColor = randomColor(random);

        //先给背景上色
        g.setColor(backColor);
        g.fillRect(0, 0, width, height);

        //绘制干扰线
        g.setStroke(new BasicStroke(stroke));//干扰线粗细
        for (int i = 0; i < 20; i++) {
            //设置线条的颜色
            g.setColor(randomColor(random));
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width * 2) - width / 2, random.nextInt(height * 2) - height / 2);
        }

        //绘制验证码
        //Font.deriveFont(int style, float size)通过复制此 Font 对象并应用新样式和大小，创建一个新 Font 对象
        //从我们的字体库里随机取一种字体并重新设置样式大小
        final Font font = FONTS[random.nextInt(FONTS.length)].deriveFont(Font.BOLD, fontSize);
        g.setFont(font);
        //通过这个类获取字体的宽高 以保证验证码画在图片合适的位置
        final FontMetrics fontMetrics = g.getFontMetrics();
        //确定每个字符的位置
        final int bodyX = width / code.length();
        for (int i = 0; i < code.length(); i++) {
            //取第i个字符
            final String str = code.substring(i, i + 1);
            //字体宽度
            final int strWidth = fontMetrics.stringWidth(str);
            //字体高度
            final int strHeight = fontMetrics.getHeight();
            //基线baseline之上的长度
            final int ascent = fontMetrics.getAscent();
            //xy为每个字符的中心坐标
            final float x = i * bodyX + bodyX / 2f;
            final float y = height / 2f;
            Color fontColor = randomColor(random);
            //获取一个和背景色相差比较大的颜色 取值0到510 越大与背景色越好区分 这里取100表示色差小于100的话就创新取色
            while (tolerance(backColor, fontColor) < MIN_TOLERANCE) {
                fontColor = randomColor(random);
            }
            g.setColor(fontColor);
            //角度在-45°到45°之间
            final double angle = random.nextDouble() * Math.PI / 2 - Math.PI / 4;
            //旋转画笔
            g.rotate(angle, x, y);
            //把验证码的第i个字符画到图片上
            g.drawString(str, x - strWidth / 2F, y + ascent - strHeight / 2F);
            //再转回来
            g.rotate(-angle, x, y);
        }

        //绘制干扰线
        for (int i = 0; i < 5; i++) {
            //设置线条的颜色
            g.setColor(randomColor(random));
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width * 2) - width, random.nextInt(height * 2) - height);
        }

        //绘制噪点
        //噪声率 × 总面积 ÷ 点面积 保证图片越大噪点不会越多
        final int area = (int) (RATIO * (width * height) / (Math.PI * stroke / 2 * stroke / 2));
        final int strokeInt = stroke > 1 ? (int) stroke : 1;
        final int strokeDiv2 = (int) (stroke / 2);
        for (int i = 0; i < area; i++) {
            final int x = random.nextInt(width) - strokeDiv2;
            final int y = random.nextInt(height) - strokeDiv2;
            g.setColor(randomColor(random));
            g.fillOval(x, y, strokeInt, strokeInt);
        }

        //<img src="data:image/png;base64,(你的base64编码)"/>
        //给验证码随机生成一个id
        final String id = randomStr(32);
        return new Captcha(id, code, image);
    }

}
